package gateway

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"sync"
)

type GateWay struct {
	tree *node
	// The middleware stack
	middlewares []func(http.Handler) http.Handler
	inline bool
	parent *GateWay
	handler http.Handler
	pool *sync.Pool
	notFoundHandler http.HandlerFunc
	methodNotAllowedHandler http.HandlerFunc
}

//func (gate *GateWay) Routes() []Route {
//	panic("implement me")
//}

//func (gate *GateWay) Match(rctx *Context, method, path string) bool {
//	panic("implement me")
//}

func NewGateway() *GateWay {
	gate := &GateWay{tree: &node{}, pool: &sync.Pool{}}
	gate.pool.New = func() interface{} {
		return NewRouteContext()
	}
	return gate
}

func (gate *GateWay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Ensure the GateWay has some routes defined on the GateWay
	if gate.handler == nil {
		gate.NotFoundHandler().ServeHTTP(w, r)
		return
	}

	// Check if a routing context already exists from a parent router.
	rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
	if rctx != nil {
		gate.handler.ServeHTTP(w, r)
		return
	}

	// Fetch a RouteContext object from the sync pool, and call the computed
	// gate.handler that is comprised of gate.middlewares + gate.routeHTTP.
	// Once the request is finished, reset the routing context and put it back
	// into the pool for reuse from another request.
	rctx = gate.pool.Get().(*Context)
	rctx.Reset()
	rctx.Routes = gate
	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
	gate.handler.ServeHTTP(w, r)
	gate.pool.Put(rctx)
}
func (gate *GateWay)Use(middlewares ...func(http.Handler) http.Handler)  {
	if gate.handler != nil {
		panic("gateway: all middlewares must be defined before routes on a GateWay")
	}
	gate.middlewares = append(gate.middlewares, middlewares...)
}

func (gate *GateWay) Handle(pattern string, handler http.Handler) {
	gate.handle(mALL, pattern, handler)
}

func (gate *GateWay) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mALL, pattern, handlerFn)
}
// Method adds the route `pattern` that matches `method` http method to
// execute the `handler` http.Handler.
func (gate *GateWay) Method(method, pattern string, handler http.Handler) {
	m, ok := methodMap[strings.ToUpper(method)]
	if !ok {
		panic(fmt.Sprintf("chi: '%s' http method is not supported.", method))
	}
	gate.handle(m, pattern, handler)
}

// MethodFunc adds the route `pattern` that matches `method` http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {
	gate.Method(method, pattern, handlerFn)
}

// Connect adds the route `pattern` that matches a CONNECT http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Connect(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mCONNECT, pattern, handlerFn)
}

// Delete adds the route `pattern` that matches a DELETE http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Delete(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mDELETE, pattern, handlerFn)
}

// Get adds the route `pattern` that matches a GET http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Get(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mGET, pattern, handlerFn)
}

// Head adds the route `pattern` that matches a HEAD http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Head(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mHEAD, pattern, handlerFn)
}

// Options adds the route `pattern` that matches a OPTIONS http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Options(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mOPTIONS, pattern, handlerFn)
}

// Patch adds the route `pattern` that matches a PATCH http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Patch(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mPATCH, pattern, handlerFn)
}

// Post adds the route `pattern` that matches a POST http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Post(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mPOST, pattern, handlerFn)
}

// Put adds the route `pattern` that matches a PUT http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Put(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mPUT, pattern, handlerFn)
}

// Trace adds the route `pattern` that matches a TRACE http method to
// execute the `handlerFn` http.HandlerFunc.
func (gate *GateWay) Trace(pattern string, handlerFn http.HandlerFunc) {
	gate.handle(mTRACE, pattern, handlerFn)
}

// NotFound sets a custom http.HandlerFunc for routing paths that could
// not be found. The default 404 handler is `http.NotFound`.
func (gate *GateWay) NotFound(handlerFn http.HandlerFunc) {
	// Build NotFound handler chain
	m := gate
	hFn := handlerFn
	if gate.inline && gate.parent != nil {
		m = gate.parent
		hFn = Chain(gate.middlewares...).HandlerFunc(hFn).ServeHTTP
	}

	// Update the notFoundHandler from this point forward
	m.notFoundHandler = hFn
	m.updateSubRoutes(func(subGateWay *GateWay) {
		if subGateWay.notFoundHandler == nil {
			subGateWay.NotFound(hFn)
		}
	})
}

// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the
// method is unresolved. The default handler returns a 405 with an empty body.
func (gate *GateWay) MethodNotAllowed(handlerFn http.HandlerFunc) {
	// Build MethodNotAllowed handler chain
	m := gate
	hFn := handlerFn
	if gate.inline && gate.parent != nil {
		m = gate.parent
		hFn = Chain(gate.middlewares...).HandlerFunc(hFn).ServeHTTP
	}

	// Update the methodNotAllowedHandler from this point forward
	m.methodNotAllowedHandler = hFn
	m.updateSubRoutes(func(subGateWay *GateWay) {
		if subGateWay.methodNotAllowedHandler == nil {
			subGateWay.MethodNotAllowed(hFn)
		}
	})
}

// With adds inline middlewares for an endpoint handler.
func (gate *GateWay) With(middlewares ...func(http.Handler) http.Handler) Router {
	// Similarly as in handle(), we must build the GateWay handler once further
	// middleware registration isn't allowed for this stack, like now.
	if !gate.inline && gate.handler == nil {
		gate.buildRouteHandler()
	}

	// Copy middlewares from parent inline GateWays
	var mws Middlewares
	if gate.inline {
		mws = make(Middlewares, len(gate.middlewares))
		copy(mws, gate.middlewares)
	}
	mws = append(mws, middlewares...)

	im := &GateWay{
		pool: gate.pool, inline: true, parent: gate, tree: gate.tree, middlewares: mws,
		notFoundHandler: gate.notFoundHandler, methodNotAllowedHandler: gate.methodNotAllowedHandler,
	}

	return im
}

// Group creates a new inline-GateWay with a fresh middleware stack. It's useful
// for a group of handlers along the same routing path that use an additional
// set of middlewares. See _examples/.
func (gate *GateWay) Group(fn func(r Router)) Router {
	im := gate.With().(*GateWay)
	if fn != nil {
		fn(im)
	}
	return im
}

// Route creates a new GateWay with a fresh middleware stack and mounts it
// along the `pattern` as a subrouter. Effectively, this is a short-hand
// call to Mount. See _examples/.
func (gate *GateWay) Route(pattern string, fn func(r Router)) Router {
	subRouter := NewRouter()
	if fn != nil {
		fn(subRouter)
	}
	gate.Mount(pattern, subRouter)
	return subRouter
}

// Mount attaches another http.Handler or chi Router as a subrouter along a routing
// path. It's very useful to split up a large API as many independent routers and
// compose them as a single service using Mount. See _examples/.
//
// Note that Mount() simply sets a wildcard along the `pattern` that will continue
// routing at the `handler`, which in most cases is another Router. As a result,
// if you define two Mount() routes on the exact same pattern the mount will panic.
func (gate *GateWay) Mount(pattern string, handler http.Handler) {
	// Provide runtime safety for ensuring a pattern isn't mounted on an existing
	// routing pattern.
	if gate.tree.findPattern(pattern+"*") || gate.tree.findPattern(pattern+"/*") {
		panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern))
	}

	// Assign sub-Router's with the parent not found & method not allowed handler if not specified.
	subr, ok := handler.(*GateWay)
	if ok && subr.notFoundHandler == nil && gate.notFoundHandler != nil {
		subr.NotFound(gate.notFoundHandler)
	}
	if ok && subr.methodNotAllowedHandler == nil && gate.methodNotAllowedHandler != nil {
		subr.MethodNotAllowed(gate.methodNotAllowedHandler)
	}

	// Wrap the sub-router in a handlerFunc to scope the request path for routing.
	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rctx := RouteContext(r.Context())
		rctx.RoutePath = gate.nextRoutePath(rctx)
		handler.ServeHTTP(w, r)
	})

	if pattern == "" || pattern[len(pattern)-1] != '/' {
		gate.handle(mALL|mSTUB, pattern, mountHandler)
		gate.handle(mALL|mSTUB, pattern+"/", mountHandler)
		pattern += "/"
	}

	method := mALL
	subroutes, _ := handler.(Routes)
	if subroutes != nil {
		method |= mSTUB
	}
	n := gate.handle(method, pattern+"*", mountHandler)

	if subroutes != nil {
		n.subroutes = subroutes
	}
}

// Routes returns a slice of routing information from the tree,
// useful for traversing available routes of a router.
func (gate *GateWay) Routes() []Route {
	return gate.tree.routes()
}

// Middlewares returns a slice of middleware handler functions.
func (gate *GateWay) Middlewares() Middlewares {
	return gate.middlewares
}

// Match searches the routing tree for a handler that matches the method/path.
// It's similar to routing a http request, but without executing the handler
// thereafter.
//
// Note: the *Context state is updated during execution, so manage
// the state carefully or make a NewRouteContext().
func (gate *GateWay) Match(rctx *Context, method, path string) bool {
	m, ok := methodMap[method]
	if !ok {
		return false
	}

	node, _, h := gate.tree.FindRoute(rctx, m, path)

	if node != nil && node.subroutes != nil {
		rctx.RoutePath = gate.nextRoutePath(rctx)
		//return node.subroutes.Match(rctx, method, rctx.RoutePath)
	}

	return h != nil
}

// NotFoundHandler returns the default GateWay 404 responder whenever a route
// cannot be found.
func (gate *GateWay) NotFoundHandler() http.HandlerFunc {
	if gate.notFoundHandler != nil {
		return gate.notFoundHandler
	}
	return http.NotFound
}

// MethodNotAllowedHandler returns the default GateWay 405 responder whenever
// a method cannot be resolved for a route.
func (gate *GateWay) MethodNotAllowedHandler() http.HandlerFunc {
	if gate.methodNotAllowedHandler != nil {
		return gate.methodNotAllowedHandler
	}
	return methodNotAllowedHandler
}

// buildRouteHandler builds the single GateWay handler that is a chain of the middleware
// stack, as defined by calls to Use(), and the tree router (GateWay) itself. After this
// point, no other middlewares can be registered on this GateWay's stack. But you can still
// compose additional middlewares via Group()'s or using a chained middleware handler.
func (gate *GateWay) buildRouteHandler() {
	gate.handler = chain(gate.middlewares, http.HandlerFunc(gate.routeHTTP))
}

// handle registers a http.Handler in the routing tree for a particular http method
// and routing pattern.
func (gate *GateWay) handle(method methodTyp, pattern string, handler http.Handler) *node {
	if len(pattern) == 0 || pattern[0] != '/' {
		panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern))
	}

	// Build the final routing handler for this GateWay.
	if !gate.inline && gate.handler == nil {
		gate.buildRouteHandler()
	}

	// Build endpoint handler with inline middlewares for the route
	var h http.Handler
	if gate.inline {
		gate.handler = http.HandlerFunc(gate.routeHTTP)
		h = Chain(gate.middlewares...).Handler(handler)
	} else {
		h = handler
	}

	// Add the endpoint to the tree and return the node
	return gate.tree.InsertRoute(method, pattern, h)
}

// routeHTTP routes a http.Request through the GateWay routing tree to serve
// the matching handler for a particular http method.
func (gate *GateWay) routeHTTP(w http.ResponseWriter, r *http.Request) {
	// Grab the route context object
	rctx := r.Context().Value(RouteCtxKey).(*Context)

	// The request routing path
	routePath := rctx.RoutePath
	if routePath == "" {
		if r.URL.RawPath != "" {
			routePath = r.URL.RawPath
		} else {
			routePath = r.URL.Path
		}
	}

	// Check if method is supported by chi
	if rctx.RouteMethod == "" {
		rctx.RouteMethod = r.Method
	}
	method, ok := methodMap[rctx.RouteMethod]
	if !ok {
		gate.MethodNotAllowedHandler().ServeHTTP(w, r)
		return
	}

	// Find the route
	if _, _, h := gate.tree.FindRoute(rctx, method, routePath); h != nil {
		h.ServeHTTP(w, r)
		return
	}
	if rctx.methodNotAllowed {
		gate.MethodNotAllowedHandler().ServeHTTP(w, r)
	} else {
		gate.NotFoundHandler().ServeHTTP(w, r)
	}
}

func (gate *GateWay) nextRoutePath(rctx *Context) string {
	routePath := "/"
	nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
	if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
		routePath += rctx.routeParams.Values[nx]
	}
	return routePath
}

// Recursively update data on child routers.
func (gate *GateWay) updateSubRoutes(fn func(subGateWay *GateWay)) {
	for _, r := range gate.tree.routes() {
		subGateWay, ok := r.SubRoutes.(*GateWay)
		if !ok {
			continue
		}
		fn(subGateWay)
	}
}

// methodNotAllowedHandler is a helper function to respond with a 405,
// method not allowed.
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(405)
	w.Write(nil)
}